{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "8f9cbe9d",
   "metadata": {},
   "source": [
    "# Binding\n",
    "\n",
    "- Author: [Wonyoung Lee](https://github.com/BaBetterB)\n",
    "- Peer Review: \n",
    "- Proofread : [Chaeyoon Kim](https://github.com/chaeyoonyunakim)\n",
    "- This is a part of [LangChain Open Tutorial](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial)\n",
    "\n",
    "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/13-LangChain-Expression-Language/10-Binding.ipynb)[![Open in GitHub](https://img.shields.io/badge/Open%20in%20GitHub-181717?style=flat-square&logo=github&logoColor=white)](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/13-LangChain-Expression-Language/10-Binding.ipynb)\n",
    "\n",
    "## Overview\n",
    "\n",
    "This tutorial covers a scenario where you need to pass constant arguments(not included in the output of the previous Runnable or user input) when calling a Runnable inside a Runnable sequence. In such cases, ```Runnable.bind()``` is a convenient way to pass these arguments\n",
    "\n",
    "\n",
    "### Table of Contents\n",
    "\n",
    "- [Overview](#overview)\n",
    "- [Environment Setup](#environment-setup)\n",
    "- [Runtime Arguments Binding](#runtime-arguments-binding)\n",
    "- [Connecting OpenAI Functions](#connecting-openai-functions)\n",
    "- [Connecting OpenAI Tools](#connecting-openai-tools)\n",
    "\n",
    "### References\n",
    "\n",
    "- [LangChain RunnablePassthrough API reference](https://python.langchain.com/api_reference/core/runnables/langchain_core.runnables.passthrough.RunnablePassthrough.html)\n",
    "- [LangChain ChatPromptTemplate API reference](https://python.langchain.com/api_reference/core/prompts/langchain_core.prompts.chat.ChatPromptTemplate.html)\n",
    "\n",
    "----\n",
    "\n",
    " \n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "89446018",
   "metadata": {},
   "source": [
    "## Environment Setup\n",
    "\n",
    "Set up the environment. You may refer to [Environment Setup](https://wikidocs.net/257836) for more details.\n",
    "\n",
    "**[Note]**\n",
    "- ```langchain-opentutorial``` is a package that provides a set of easy-to-use environment setup tools, useful functions and utilities for tutorials. \n",
    "- You can check out the [ ```langchain-opentutorial``` ](https://github.com/LangChain-OpenTutorial/langchain-opentutorial-pypi) for more details."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "499a5cf5",
   "metadata": {},
   "source": [
    "Load sample text and output the content."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "13991fe2",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n",
      "[notice] A new release of pip is available: 24.2 -> 24.3.1\n",
      "[notice] To update, run: python.exe -m pip install --upgrade pip\n"
     ]
    }
   ],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install langchain-opentutorial"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "9f8267d6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Install required packages\n",
    "from langchain_opentutorial import package\n",
    "\n",
    "\n",
    "package.install(\n",
    "    [\n",
    "        \"langsmith\",\n",
    "        \"langchain\",\n",
    "        \"langchain_core\",\n",
    "        \"langchain_openai\",\n",
    "    ],\n",
    "    verbose=False,\n",
    "    upgrade=False,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "c38e67c0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Environment variables have been set successfully.\n"
     ]
    }
   ],
   "source": [
    "# Set environment variables\n",
    "from langchain_opentutorial import set_env\n",
    "\n",
    "set_env(\n",
    "    {\n",
    "        \"OPENAI_API_KEY\": \"\",\n",
    "        \"LANGCHAIN_API_KEY\": \"\",\n",
    "        \"LANGCHAIN_TRACING_V2\": \"true\",\n",
    "        \"LANGCHAIN_ENDPOINT\": \"https://api.smith.langchain.com\",\n",
    "        \"LANGCHAIN_PROJECT\": \"Binding\",  # title\n",
    "    }\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7d1632bb",
   "metadata": {},
   "source": [
    "You can alternatively set ```OPENAI_API_KEY``` in ```.env``` file and load it.\n",
    "\n",
    "[Note] This is not necessary if you've already set ```OPENAI_API_KEY``` in previous steps."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "5bc25432",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Configuration File for Managing API Keys as Environment Variables\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "# Load API Key Information\n",
    "load_dotenv(override=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3fb790a1",
   "metadata": {},
   "source": [
    "## Runtime Arguments Binding\n",
    "\n",
    "This section explains how to use ```Runnable.bind()``` to pass constant arguments to a ```Runnable``` within a sequence, especially when those arguments aren't part of the previous Runnable's output or user input.\n",
    "\n",
    "**Passing variables to prompts:**\n",
    "\n",
    "1. Use ```RunnablePassthrough``` to pass the ```{equation_statement}``` variable to the prompt.\n",
    "2. Use ```StrOutputParser``` to parse the model's output into a string, creating a ```runnable``` object.\n",
    "3. Call the ```runnable.invoke()``` method to pass the equation statement (e.g., \\\"x raised to the third plus seven equals 12\\\") and get the result."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "649b24d7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "EQUATION: x^3 + 7 = 12\n",
      "\n",
      "SOLUTION:\n",
      "1. Subtract 7 from both sides of the equation to isolate the x^3 term:\n",
      "   x^3 + 7 - 7 = 12 - 7\n",
      "   x^3 = 5\n",
      "\n",
      "2. Take the cube root of both sides to solve for x:\n",
      "   x = 5^(1/3)\n",
      "\n",
      "Therefore, the solution is:\n",
      "x ≈ 1.71 (rounded to two decimal places)\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.output_parsers import StrOutputParser\n",
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "from langchain_core.runnables import RunnablePassthrough\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\n",
    "            \"system\",\n",
    "            # Write the following equation using algebraic symbols and then solve it.\n",
    "            \"Write out the following equation using algebraic symbols then solve it. \"\n",
    "            \"Please avoid LaTeX-style formatting and use plain symbols.\"\n",
    "            \"Use the format:\\n\\nEQUATION:...\\nSOLUTION:...\\n\",\n",
    "        ),\n",
    "        (\n",
    "            \"human\",\n",
    "            \"{equation_statement}\",  # Accepts the equation statement from the user as a variable.\n",
    "        ),\n",
    "    ]\n",
    ")\n",
    "# Initialize the ChatOpenAI model and set temperature to 0.\n",
    "model = ChatOpenAI(model=\"gpt-4o\", temperature=0)\n",
    "\n",
    "# Pass the equation statement to the prompt and parse the model's output as a string.\n",
    "runnable = (\n",
    "    {\"equation_statement\": RunnablePassthrough()} | prompt | model | StrOutputParser()\n",
    ")\n",
    "\n",
    "# Input an example equation statement and print the result.\n",
    "result = runnable.invoke(\"x raised to the third plus seven equals 12\")\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ed4ced2f",
   "metadata": {},
   "source": [
    "**Using bind() method with stop words**\n",
    "\n",
    "For controlling the end of the model's output using a specific stop word, you can use ```model.bind()``` to instruct the model to halt its generation upon encountering the stop token such as ```SOLUTION```."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "9d94b8e3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "EQUATION: x^3 + 7 = 12\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "runnable = (\n",
    "    # Create a runnable passthrough object and assign it to the \"equation_statement\" key.\n",
    "    {\"equation_statement\": RunnablePassthrough()}\n",
    "    | prompt  # Add the prompt to the pipeline.\n",
    "    | model.bind(\n",
    "        stop=\"SOLUTION\"\n",
    "    )  # Bind the model and set it to stop generating at the \"SOLUTION\" token.\n",
    "    | StrOutputParser()  # Add the string output parser to the pipeline.\n",
    ")\n",
    "# Execute the pipeline with the input \"x raised to the third plus seven equals 12\" and print the result.\n",
    "print(runnable.invoke(\"x raised to the third plus seven equals 12\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9be5d575",
   "metadata": {},
   "source": [
    "## Connecting OpenAI Functions\n",
    "\n",
    "```bind()``` is particularly useful for connecting OpenAI Functions with compatible OpenAI models.\n",
    "\n",
    "Let's define ```openai_function``` according to a schema."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "0b7cd50c",
   "metadata": {},
   "outputs": [],
   "source": [
    "openai_function = {\n",
    "    \"name\": \"solver\",  # Function name\n",
    "    # Function description: Formulate and solve an equation.\n",
    "    \"description\": \"Formulates and solves an equation\",\n",
    "    \"parameters\": {  # Function parameters\n",
    "        \"type\": \"object\",  # Parameter type: object\n",
    "        \"properties\": {  # Parameter properties\n",
    "            \"equation\": {  # Equation property\n",
    "                \"type\": \"string\",  # Type: string\n",
    "                \"description\": \"The algebraic expression of the equation\",  # Description\n",
    "            },\n",
    "            \"solution\": {  # Solution property\n",
    "                \"type\": \"string\",  # Type: string\n",
    "                \"description\": \"The solution to the equation\",  # Description\n",
    "            },\n",
    "        },\n",
    "        \"required\": [\n",
    "            \"equation\",\n",
    "            \"solution\",\n",
    "        ],  # Required parameters: equation and solution\n",
    "    },\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b3294828",
   "metadata": {},
   "source": [
    "**Binding the solver function.**\n",
    "\n",
    "We can then use the ```bind()``` method to associate a function call (like ```solver```) with the language model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "edb2c5a4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"equation\":\"x^3 + 7 = 12\",\"solution\":\"x^3 = 12 - 7; x^3 = 5; x = 5^(1/3)\"}', 'name': 'solver'}, 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 44, 'prompt_tokens': 95, 'total_tokens': 139, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_50cad350e4', 'finish_reason': 'stop', 'logprobs': None}, id='run-bb333525-2117-4a09-bf1c-c6bdca21b50c-0', usage_metadata={'input_tokens': 95, 'output_tokens': 44, 'total_tokens': 139, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Write the following equation using algebraic symbols and then solve it\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\n",
    "            \"system\",\n",
    "            \"Write out the following equation using algebraic symbols then solve it.\",\n",
    "        ),\n",
    "        (\"human\", \"{equation_statement}\"),\n",
    "    ]\n",
    ")\n",
    "\n",
    "\n",
    "model = ChatOpenAI(model=\"gpt-4o\", temperature=0).bind(\n",
    "    function_call={\"name\": \"solver\"},  # Bind the OpenAI function schema\n",
    "    functions=[openai_function],\n",
    ")\n",
    "\n",
    "\n",
    "runnable = {\"equation_statement\": RunnablePassthrough()} | prompt | model\n",
    "\n",
    "\n",
    "# Equation: x raised to the third plus seven equals 12\n",
    "\n",
    "\n",
    "runnable.invoke(\"x raised to the third plus seven equals 12\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6c4904a4",
   "metadata": {},
   "source": [
    "## Connecting OpenAI Tools\n",
    "\n",
    "This section explains how to connect and use OpenAI tools within your LangChain applications.\n",
    "The ```tools``` object simplifies using various OpenAI features.\n",
    "For example, calling the ```tool.run``` method with a natural language query allows the model to utilize the specified tool to generate a response."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "2fce5808",
   "metadata": {},
   "outputs": [],
   "source": [
    "tools = [\n",
    "    {\n",
    "        \"type\": \"function\",\n",
    "        \"function\": {\n",
    "            \"name\": \"get_current_weather\",  # Function name to get current weather\n",
    "            \"description\": \"Fetches the current weather for a given location\",  # Description\n",
    "            \"parameters\": {\n",
    "                \"type\": \"object\",\n",
    "                \"properties\": {\n",
    "                    \"location\": {\n",
    "                        \"type\": \"string\",\n",
    "                        \"description\": \"City and state, e.g.: San Francisco, CA\",  # Location description\n",
    "                    },\n",
    "                    # Temperature unit\n",
    "                    \"unit\": {\"type\": \"string\", \"enum\": [\"celsius\", \"fahrenheit\"]},\n",
    "                },\n",
    "                \"required\": [\"location\"],  # Required parameter: location\n",
    "            },\n",
    "        },\n",
    "    }\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8a51880d",
   "metadata": {},
   "source": [
    "**Binding tools and invoking the model:**\n",
    "\n",
    "1. Use ```bind()``` to associate ```tools``` with the language model.\n",
    "2. Call the ```invoke()``` method on the bound model, by providing a natural language question as input.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "3b5c0149",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_ixydz9CyFSyB0LHUATftfdXA', 'function': {'arguments': '{\"location\": \"San Francisco, CA\"}', 'name': 'get_current_weather'}, 'type': 'function'}, {'id': 'call_VFAGF4YanFQVg1lJQ9x1miR3', 'function': {'arguments': '{\"location\": \"New York, NY\"}', 'name': 'get_current_weather'}, 'type': 'function'}, {'id': 'call_RMYL8pWFlMarWkOPkigKQSug', 'function': {'arguments': '{\"location\": \"Los Angeles, CA\"}', 'name': 'get_current_weather'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 71, 'prompt_tokens': 90, 'total_tokens': 161, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_50cad350e4', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-50665b85-c055-413a-8a40-3c1070aa3c45-0', tool_calls=[{'name': 'get_current_weather', 'args': {'location': 'San Francisco, CA'}, 'id': 'call_ixydz9CyFSyB0LHUATftfdXA', 'type': 'tool_call'}, {'name': 'get_current_weather', 'args': {'location': 'New York, NY'}, 'id': 'call_VFAGF4YanFQVg1lJQ9x1miR3', 'type': 'tool_call'}, {'name': 'get_current_weather', 'args': {'location': 'Los Angeles, CA'}, 'id': 'call_RMYL8pWFlMarWkOPkigKQSug', 'type': 'tool_call'}], usage_metadata={'input_tokens': 90, 'output_tokens': 71, 'total_tokens': 161, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Initialize the ChatOpenAI model and bind the tools.\n",
    "model = ChatOpenAI(model=\"gpt-4o\").bind(tools=tools)\n",
    "# Invoke the model to ask about the weather in San Francisco, New York, and Los Angeles.\n",
    "model.invoke(\n",
    "    \"Can you tell me the current weather in San Francisco, New York, and Los Angeles?\"\n",
    ")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain-opentutorial-HDS-w_h3-py3.11",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
